/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package tp_sd_server.model;

import java.io.EOFException;
import java.io.IOException;
import java.io.InvalidClassException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OptionalDataException;
import java.io.StreamCorruptedException;
import java.io.UTFDataFormatException;
import java.net.Socket;
import java.net.SocketException;
import java.util.List;
import tp_sd_base.IServer;
import tp_sd_base.User;

/**
 *
 * @author 
 */
public class ClientThread {

    private Socket socket;
    
    private ObjectOutputStream outputStream;

    private ObjectInputStream inputStream;

    private String login;

    public ClientThread(Socket socket) {
        try {
            this.socket = socket;
            outputStream = new ObjectOutputStream(socket.getOutputStream());
            inputStream = new ObjectInputStream(socket.getInputStream());
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    public void run() {
        try {
            int num = inputStream.readInt();
            IServer.MESSAGE_TYPE message_type = IServer.MESSAGE_TYPE.values()[num];
            if (message_type.equals(IServer.MESSAGE_TYPE.LOGIN) || message_type.equals(IServer.MESSAGE_TYPE.REGISTRATION)) {
                String nick = inputStream.readUTF().toUpperCase();
                String pass = inputStream.readUTF().toUpperCase();

                boolean registration = message_type.equals(IServer.MESSAGE_TYPE.REGISTRATION);
                if (registration ? ServerChat.register(nick, pass, this) : ServerChat.login(nick, pass, this)) {
                    MessageLog.getInstance().addMessage("Cliente " + (registration ? "Registrado" : "autenticado") + " com sucesso");
                    login =nick;
                    outputStream.writeInt(IServer.MESSAGE_TYPE.LOGIN.ordinal());
                    outputStream.writeUTF(nick);
                    outputStream.flush();
                } else {
                    String msg = registration ? "O nome escolhido já existe" : "Login Inválido";
                    MessageLog.getInstance().addMessage(msg);
                    sendError(IServer.ERROR_TYPE.LOGIN, msg);
                    return;
                }
            } else {
                MessageLog.getInstance().addMessage("Parâmetros inválidos");
                sendError(IServer.ERROR_TYPE.INVALID_ARGS, "Parâmetros inválidos");
                return;
            }

            try {
                while (!socket.isClosed()) {
                    readMessage();
                }
            } catch (ClassNotFoundException ex) {
                MessageLog.getInstance().addMessage(ex.getMessage());
            } catch (IOException ioe) {
                MessageLog.getInstance().addMessage(ioe.getMessage());
            }
        } catch (IOException ex) {
            MessageLog.getInstance().addMessage(ex.getMessage());
        } catch (Exception e) {
            MessageLog.getInstance().addMessage(e.getMessage());
        } finally {
            try {
                socket.close();
            } catch (IOException ex) {
                MessageLog.getInstance().addMessage(ex.getMessage());
                ServerChat.removeConnectedUser(login);
            }
        }
    }

    public void readMessage() throws IOException, ClassNotFoundException {
        try {
            int value = inputStream.readInt();
            try {
                switch (getMessageType(value)) {
                    case DELETE:
                        onDelete((List<String>) inputStream.readObject());
                        break;
                    case INSERT:
                        onInsert((List<String>) inputStream.readObject());
                        break;
                    case LOGOUT:
                        onLogOut();
                        break;
                    case MESSAGE:
                        onMessage(inputStream.readUTF());
                        break;
                    case RANKING:
                        onRanking(inputStream.readInt(),inputStream.readInt());
                        break;
                    case REFRESH:
                        onRefresh(inputStream.readInt());
                        break;
                    default:
                        onInvalidOperation(value);
                }
            } catch (ArrayIndexOutOfBoundsException ex) { // Cosiderar os códigos de erro como operação inválida
                onInvalidOperation(value);
            } catch (UTFDataFormatException ex) {
                onInvalidArgs();
            } catch (EOFException ex) {
                onInvalidArgs();
            } catch (ClassNotFoundException ex) {
                onInvalidArgs();
            } catch (InvalidClassException ex) {
                onInvalidArgs();
            } catch (StreamCorruptedException ex) {
                onInvalidArgs();
            } catch (OptionalDataException ex) {
                onInvalidArgs();
            } catch (ClassCastException ex) {
                onInvalidArgs();
            } catch (IOException ex) {
                onInvalidArgs();
            }
        } catch (EOFException eofe) {
            onInvalidArgs();
        } catch (SocketException se) {
            onInvalidArgs();
        }
    }



    public synchronized void sendMessage(int type,Integer type1,Integer type2,String user,String st,Object object) throws IOException{
            outputStream.writeInt(type);
            if(type1!=null)
                outputStream.writeInt(type1);
            if(type2!=null)
                outputStream.writeInt(type2);

            if(user!=null){
                outputStream.writeUTF(user);
            }
            
            
                if(st!=null)
                    outputStream.writeUTF(st);
                if(object!=null)
                    outputStream.writeObject(object);
            
            outputStream.flush();
    }
    
    public void sendMessage(IServer.MESSAGE_TYPE message_type, User sender, String msg) throws IOException {
        sendMessage(message_type.ordinal(),null,null, sender.getNickName(),msg,null);
    }

    public void sendError(IServer.ERROR_TYPE message_type, String msg) throws IOException, SocketException {
        sendMessage(IServer.MESSAGE_TYPE.ERROR.ordinal(),message_type.ordinal(),null, null,msg,null);
        disconnect();
    }

    public void disconnect() {
        try {
            if (socket != null && !socket.isClosed()) {
                socket.close();
            }
            ServerChat.removeConnectedUser(login);
        } catch (IOException ex) {
            MessageLog.getInstance().addMessage(ex.getMessage());
        }
    }


    protected static IServer.MESSAGE_TYPE getMessageType(int bs) {
        return IServer.MESSAGE_TYPE.values()[bs];
    }

    protected static IServer.ERROR_TYPE getErrorType(int bs) {
        return IServer.ERROR_TYPE.values()[bs];
    }

    protected static IServer.REFRESH_TYPE getRefreshType(int bs) {
        return IServer.REFRESH_TYPE.values()[bs];
    }

    private void onDelete(List<String> list) {
        ServerChat.removeAssociationUser(login, list);
    }

    private void onInsert(List<String> list) {
        ServerChat.addAssociationUser(login, list);
    }

    private void onLogOut() {
        disconnect();
    }

    private void onMessage(String readUTF) {
        ServerChat.handlersMessages(login, readUTF);
    }

    private void onRanking(int readInt,int numTop) {
        try {
            sendMessage(IServer.MESSAGE_TYPE.RANKING.ordinal(), readInt, null, null, null, readInt == IServer.RANKING.FOLLOWERS.ordinal() ? ServerChat.getTopSeguidos(numTop) : ServerChat.getTopUsers(numTop));
        } catch (IOException ex) {
            MessageLog.getInstance().addMessage(ex.getMessage());
            disconnect();
        }

    }

    private void onRefresh(int readInt) {
        try {
            switch (IServer.REFRESH_TYPE.values()[readInt]) {
                case FOLLOWED:
                    sendMessage(IServer.MESSAGE_TYPE.REFRESH.ordinal(), IServer.REFRESH_TYPE.FOLLOWED.ordinal(), null,null, null, ServerChat.findAllFollowed(login));
                    break;
                case FOLLOWERS:
                    sendMessage(IServer.MESSAGE_TYPE.REFRESH.ordinal(), IServer.REFRESH_TYPE.FOLLOWERS.ordinal(),null ,null, null, ServerChat.findAllFollowers(login));
                    break;
                case ONLINE:
                    sendMessage(IServer.MESSAGE_TYPE.REFRESH.ordinal(), IServer.REFRESH_TYPE.ONLINE.ordinal(), null,null, null, ServerChat.findAllOnline());
                    break;
            }
        } catch (IOException ex) {
            MessageLog.getInstance().addMessage(ex.getMessage());
        }
    }

    private void onInvalidOperation(int value) {
        disconnect();
    }

    private void onInvalidArgs() {
        disconnect();
    }
    
}
